package org.fastiot.data

import grails.converters.JSON
import org.grails.web.json.JSONElement
import org.springframework.validation.FieldError

class AggregationRules {

    static class FieldDescription implements grails.validation.Validateable {
        private final static groovyLoader = new GroovyClassLoader()
        private final static PathScriptClassCache = [:]

        /**
         * 字段标识
         */
        String code
        /**
         * 字段名称
         */
        String name
        /**
         * 获取路径
         */
        String path
        /**
         * 枚举范围
         */
        String enumRange

        /**
         * 按路径获取字段值
         * @param source 符合JSON类型的数据源，需包含所需字段的节点。
         * @return 字段值
         */
        def getFieldValueByPath(JSONElement source, boolean forcedInit = false) {
            if (source || forcedInit) {
                def scriptClass = PathScriptClassCache[path] as Class<Script>

                if (!scriptClass) {
                    scriptClass = PathScriptClassCache[path] = groovyLoader.parseClass(path)
                }

                if (source) {
                    def script = scriptClass.newInstance() as Script
                    script.binding.setVariable("x", source)
                    script.run()
                } else {
                    source
                }
            } else {
                source
            }
        }

        /**
         * 按路径获取字段值，并匹配枚举范围
         * @param source 符合JSON类型的数据源，需包含所需字段的节点。
         * @return 按枚举范围转换后值，或原值。
         */
        def getFieldValueByPathAndEnum(JSONElement source) {
            def original = getFieldValueByPath(source) as String
            if (enumRange) {
                JSON.parse(enumRange)[original]
            } else
                original
        }

        static constraints = {
            code(blank: false)
            name(blank: false)
            path(blank: false)
            enumRange(nullable: true)
        }
    }

    static belongsTo = [dataSet: DataSet]
    String dataSetCode
    String dataSetName

    Integer bindPacketCode
    String bindHexPacketCode

    FieldDescription field1
    FieldDescription field2
    FieldDescription field3
    FieldDescription field4
    FieldDescription field5
    FieldDescription field6
    FieldDescription field7
    FieldDescription field8
    static embedded = ['field1', 'field2', 'field3', 'field4', 'field5', 'field6', 'field7', 'field8']

    Date dateCreated
    Date lastUpdated

    static FieldDescriptionValidator = { int sn, FieldDescription val, def obj, def errors ->
        if (val) {
            boolean valid = val.validate()
            if (!valid) {
                val.errors.allErrors.each { FieldError error ->
                    final String field = "field$sn"
                    final String code = "$error.code"
                    errors.rejectValue(field, code, error.arguments, error.defaultMessage)
                }
            }
            return valid
        }
    }

    static constraints = {
        dataSet(nullable: false)
        dataSetCode(nullable: false)
        dataSetName(nullable: false)
        bindPacketCode(nullable: false)
        bindHexPacketCode(blank: false)
        field1(validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(1, val, obj, errors)
        })
        field2(nullable: true, validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(2, val, obj, errors)
        })
        field3(nullable: true, validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(3, val, obj, errors)
        })
        field4(nullable: true, validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(4, val, obj, errors)
        })
        field5(nullable: true, validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(5, val, obj, errors)
        })
        field6(nullable: true, validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(6, val, obj, errors)
        })
        field7(nullable: true, validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(7, val, obj, errors)
        })
        field8(nullable: true, validator: {
            FieldDescription val, def obj, def errors -> FieldDescriptionValidator(8, val, obj, errors)
        })
        dateCreated()
        lastUpdated()
    }

    def beforeValidate() {
        if (dataSet) {
            dataSetCode = dataSet.code
            dataSetName = dataSet.name
        }

        if (bindPacketCode)
            bindHexPacketCode = Integer.toHexString(bindPacketCode)
        else if (bindHexPacketCode)
            bindPacketCode = Integer.parseInt(bindHexPacketCode, 16)
    }

    def afterLoad() {
        1.upto(8) { this."field$it"?.getFieldValueByPath(null, true) }
    }
}
